/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Hashtable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;

/**
 *
 * @author jsanchez
 */
public class PKCS10CertificateRequestExt extends CertificationRequest {

    private static Hashtable algorithms = new Hashtable();
    private static Hashtable oids = new Hashtable();

    static {
        algorithms.put("MD2WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
        algorithms.put("MD2WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.2"));
        algorithms.put("MD5WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
        algorithms.put("MD5WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
        algorithms.put("RSAWITHMD5", new DERObjectIdentifier("1.2.840.113549.1.1.4"));
        algorithms.put("SHA1WITHRSAENCRYPTION", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
        algorithms.put("SHA1WITHRSA", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
        algorithms.put("SHA224WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha224WithRSAEncryption);
        algorithms.put("SHA224WITHRSA", PKCSObjectIdentifiers.sha224WithRSAEncryption);
        algorithms.put("SHA256WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha256WithRSAEncryption);
        algorithms.put("SHA256WITHRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption);
        algorithms.put("SHA384WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha384WithRSAEncryption);
        algorithms.put("SHA384WITHRSA", PKCSObjectIdentifiers.sha384WithRSAEncryption);
        algorithms.put("SHA512WITHRSAENCRYPTION", PKCSObjectIdentifiers.sha512WithRSAEncryption);
        algorithms.put("SHA512WITHRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption);
        algorithms.put("RSAWITHSHA1", new DERObjectIdentifier("1.2.840.113549.1.1.5"));
        algorithms.put("RIPEMD160WITHRSAENCRYPTION", new DERObjectIdentifier("1.3.36.3.3.1.2"));
        algorithms.put("RIPEMD160WITHRSA", new DERObjectIdentifier("1.3.36.3.3.1.2"));
        algorithms.put("SHA1WITHDSA", new DERObjectIdentifier("1.2.840.10040.4.3"));
        algorithms.put("DSAWITHSHA1", new DERObjectIdentifier("1.2.840.10040.4.3"));
        algorithms.put("SHA1WITHECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
        algorithms.put("ECDSAWITHSHA1", X9ObjectIdentifiers.ecdsa_with_SHA1);

        oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");

        oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
        oids.put(new DERObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
        oids.put(new DERObjectIdentifier("1.2.840.10040.4.3"), "DSAWITHSHA1");
        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "DSAWITHSHA1");
    }

    public PKCS10CertificateRequestExt(String signatureAlgorithm, X509Name x509Name, PublicKey publicKey, PrivateKey privateKey, String provider) {
        DERObjectIdentifier sigOID = null;
        byte[] bytes = null;
        Signature sig = null;
        ByteArrayInputStream bIn = null;
        ASN1InputStream dIn = null;
        SubjectPublicKeyInfo spki = null;
        ByteArrayOutputStream bOut = null;
        DEROutputStream dOut = null;
        try {
            sigOID = (DERObjectIdentifier) algorithms.get(signatureAlgorithm.toUpperCase());
            if (sigOID == null) {
                throw new IllegalArgumentException("Error, algoritmo de firma no v�lido para la generaci�n del PKCS10");
            }
            if (x509Name == null) {
                throw new IllegalArgumentException("Error, el sujeto no es v�lido");
            }

            if (publicKey == null) {
                throw new IllegalArgumentException("Error, clave p�blica no v�lida");
            }
            this.sigAlgId = new AlgorithmIdentifier(sigOID, null);

            bytes = publicKey.getEncoded();
            bIn = new ByteArrayInputStream(bytes);
            dIn = new ASN1InputStream(bIn);
            spki = new SubjectPublicKeyInfo((ASN1Sequence) dIn.readObject());

            this.reqInfo = new CertificationRequestInfo(x509Name, spki, null);
            try {
                sig = Signature.getInstance(sigAlgId.getObjectId().getId(), provider);
            } catch (NoSuchAlgorithmException ex) {
                ex.printStackTrace();
                sig = Signature.getInstance(signatureAlgorithm, provider);
            }
            sig.initSign(privateKey);
            bOut = new ByteArrayOutputStream();
            dOut = new DEROutputStream(bOut);
            dOut.writeObject(reqInfo);
            sig.update(bOut.toByteArray());
            this.sigBits = new DERBitString(sig.sign());
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            sigOID = null;
            bytes = null;
            sig = null;
            bIn = null;
            dIn = null;
            spki = null;
            bOut = null;
        }
    }

    public PKCS10CertificateRequestExt(byte[] certificateRequest) {
        super(toDERSequence(certificateRequest));
    }

    /**
     * M�todo que convierte una petici�n de certificado en bytes a la estructura
     * ASN1
     *
     * @param bytes petici�n de certificado en bytes
     * @return estructura ASN1
     */
    private static ASN1Sequence toDERSequence(byte[] bytes) {
        ByteArrayInputStream bIn = null;
        ASN1InputStream dIn = null;
        ASN1Sequence seq = null;
        try {
            bIn = new ByteArrayInputStream(bytes);
            dIn = new ASN1InputStream(bIn);
            seq = (ASN1Sequence) dIn.readObject();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            bIn = null;
            dIn = null;
        }
        return seq;
    }

    /**
     * Obtiene la clave p�blica de una petici�n PKCS#10
     *
     * @return clave p�blica
     */
    public PublicKey getPublicKey() {
        return getPublicKey("BC");
    }

    /**
     * Obtiene la clave p�blica de una petici�n PKCS#10
     *
     * @param provider nombre del proveedor PKCS#10
     *
     * @return clave p�blica
     */
    private PublicKey getPublicKey(String provider) {
        SubjectPublicKeyInfo subjectPKInfo = null;
        PublicKey pk = null;
        X509EncodedKeySpec xspec = null;
        AlgorithmIdentifier keyAlg = null;
        try {
            subjectPKInfo = this.reqInfo.getSubjectPublicKeyInfo();
            xspec = new X509EncodedKeySpec(new DERBitString(subjectPKInfo).getBytes());
            keyAlg = subjectPKInfo.getAlgorithmId();
            pk = KeyFactory.getInstance(keyAlg.getObjectId().getId(), provider).generatePublic(xspec);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            subjectPKInfo = null;
            xspec = null;
            keyAlg = null;
        }
        return pk;
    }

    /**
     * Verifica la firma de la petici�n PKCS#10
     *
     * @return true si la verificaci�n es correcta, false en caso contrario
     */
    public boolean verify() {
        return verify("BC");
    }

    /**
     * Verifica la firma de la petici�n PKCS#10
     *
     * @param provider nombre del proveedor JCE
     *
     * @return true si la verificaci�n es correcta, false en caso contrario
     */
    private boolean verify(String provider) {
        Signature sig = null;
        String signatureAlgorithm = null;
        boolean result = false;
        ByteArrayOutputStream bOut = null;
        try {
            try {
                if (sigAlgId.getObjectId().getId().equals("1.2.840.113549.1.1.10")) {
                    sig = Signature.getInstance("SHA256withRSAandMGF1", provider);
                } else {
                    sig = Signature.getInstance(sigAlgId.getObjectId().getId(), provider);
                }
            } catch (Exception ex) {
                 ex.printStackTrace();
                if (oids.get(sigAlgId.getObjectId().getId()) != null) {
                    signatureAlgorithm = (String) oids.get(sigAlgId.getObjectId().getId());
                    try {
                        sig = Signature.getInstance(signatureAlgorithm, provider);
                    } catch (Exception ex1) {
                         ex1.printStackTrace();
                        throw ex1;
                    }
                }
            }
            sig.initVerify(getPublicKey(provider));
            bOut = new ByteArrayOutputStream();
            DEROutputStream dOut = new DEROutputStream(bOut);

            dOut.writeObject(reqInfo);
            sig.update(bOut.toByteArray());
            result = sig.verify(sigBits.getBytes());
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            sig = null;
            signatureAlgorithm = null;
            bOut = null;
        }
        return result;
    }

    /**
     * Devuelve la codificaci�n de la petici�n PKCS#10 en DER
     *
     * @return bytes de la petici�n PKCS#10 en formato DER
     */
    @Override
    public byte[] getEncoded() {
        ByteArrayOutputStream bOS = new ByteArrayOutputStream();
        DEROutputStream derOS = new DEROutputStream(bOS);
        byte[] result = null;
        try {
            derOS.writeObject(this);
            result = bOS.toByteArray();
        } catch (Exception ex) {
             ex.printStackTrace();
        }

        return result;

    }
}
